﻿

#include "xml.h"

#include <tuple>
#include <string>
#include <fstream>

// 不要在乎我的垃圾英语。。。

using std::string;

namespace xml
{
	namespace
	{
		// 从 https://en.cppreference.com/w/cpp/string/basic_string/replace 抄的
		std::size_t replace_all(std::string& inout, const std::string& what, const std::string& with)
		{
			std::size_t count{};
			for (std::string::size_type pos{};
				inout.npos != (pos = inout.find(what.data(), pos, what.length()));
				pos += with.length(), ++count)
			{
				inout.replace(pos, what.length(), with.data(), with.length());
			}
			return count;
		}

		class XmlParser
		{
		public:
			XmlParser(const string& content): m_content(content)
			{
			}

			Elements Parse()
			{
				return _ParseContent();
			}

		private:
			Element _ParseElement()
			{
				_EatChar('<');
				auto pair = _ParseBegin();
				Element elem = std::move(std::get<0>(pair));
				const bool elemEnd = std::get<1>(pair);
				if (elemEnd)
				{
					_SkipSpace();
					return elem;
				}

				elem.subs = _ParseContent();

				const string endTag = _ParseEnd();
				if (endTag != elem.name)
					throw ParseError("open tag and close tag do not match");

				_SkipSpace();
				return elem;
			}

			// bool 是标明这个节点是否是以 /> 结尾, true 是
			std::tuple<Element, bool> _ParseBegin()
			{
				Element elem;
				elem.name = _ParseName();
				while (_ParseAttr(elem.attrs))
				{
				}

				_SkipSpace();
				const char next = _Next();

				if (next == '/')
				{
					_EatChar('>');
					return { elem, true };
				}
				if (next != '>')
					throw ParseError("expect >, but get other");
				return { elem, false };
			}

			string _ParseName()
			{
				string name;
				for (char c = _Next(); !std::isspace(c); c = _Next())
				{
					name.push_back(c);
					if (_Peek() == '>' || _Peek() == '/')
						break;
				}
				return name;
			}

			bool _ParseAttr(Attrs& attrs)
			{
				_SkipSpace();
				if (_Peek() == '/' || _Peek() == '>')
					return false;

				string attrName;
				for (char c = _Next(); c != '='; c = _Next())
					attrName.push_back(c);
				_EatChar('"');

				string attrValue;
				for (char c = _Next(); c != '"'; c = _Next())
					attrValue.push_back(c);

				attrs.emplace(attrName, attrValue);
				return true;
			}

			Elements _ParseContent()
			{
				Elements subs;
				while (true)
				{
					if (_AtEnd())
						break;
					if (_Peek() == '<')
					{
						if (_PeekNext() == '!' || _PeekNext() == '?')
						{
							_SkipTo('>');
							_EatChar('>');
							continue;
						}

						if (_PeekNext() == '/')
							break;
						subs.push_back(_ParseElement());
					}
					else
					{
						Element textElem;
						textElem.type = ElemType::Text;
						for (char c = _Next();; c = _Next())
						{
							textElem.name.push_back(c);
							if (_Peek() == '<')
								break;
						}

						replace_all(textElem.name, "&lt;", "<");
						replace_all(textElem.name, "&gt;", ">");
						replace_all(textElem.name, "&quot;", "\"");
						replace_all(textElem.name, "&apos;", "'");
						replace_all(textElem.name, "&amp;", "&");

						subs.push_back(std::move(textElem));
					}
				}

				return subs;
			}

			string _ParseEnd()
			{
				_EatChar('<');
				_EatChar('/');
				string tag;
				for (char c = _Next(); c != '>'; c = _Next())
					tag.push_back(c);
				return tag;
			}

			void _SkipSpace()
			{
				// < 127 是因为 std::isspace 的参数是 int, 而且会在 char > 255 || char < -1 的时候断言
				while (m_idx < m_content.size() && (static_cast<uint8_t>(m_content[m_idx]) < 127 && std::isspace(m_content[m_idx])))
					++m_idx;
			}

			void _SkipTo(char c)
			{
				while (_Peek() != c)
					_Next();
			}

			char _Peek() const
			{
				_CheckIdxValid(m_idx);
				return m_content[m_idx];
			}

			char _PeekNext() const
			{
				_CheckIdxValid(m_idx + 1);
				return m_content[m_idx + 1];
			}

			char _Next()
			{
				_CheckIdxValid(m_idx);
				const char c = m_content[m_idx];
				m_idx++;
				return c;
			}

			void _CheckIdxValid(int idx) const
			{
				if (m_idx >= m_content.size())
				{
					ASSERT(_AtEnd());
					// 虽然解释报错很憨批，但是这里是指，流里面的内容已经读完了, 但是解析还在继续
					throw ParseError("stream end, but parse not end");
				}
			}

			bool _AtEnd() const
			{
				return m_idx >= m_content.size();
			}

			void _EatChar(const char expect)
			{
				const char c = _Next();
				if (c == expect)
					return;
				throw ParseError("eat char error");
			}

		private:
			const string& m_content;
			long m_idx = 0;
		};
	}

	bool operator==(const Element& l, const Element& r)
	{
		return l.type == r.type && l.name == r.name && l.attrs == r.attrs && l.subs == r.subs;
	}

	Elements ParseFile(const string& fileName)
	{
		std::ifstream in(fileName);
		if (in.is_open())
		{
			const string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
			XmlParser parser(content);
			return parser.Parse();
		}
		throw ParseError("open file error");
	}

	Elements ParseString(const string& content)
	{
		XmlParser parser(content);
		return parser.Parse();
	}
}
